(function() {

    var
    $u = juliet.using({
        string : 'juliet.core.string-0.1',
        array : 'juliet.core.array-0.1',
        func : 'juliet.core.func-0.1',
        dom : 'juliet.core.dom-0.1',
        CustomPopupControl : 'juliet.ui.popup.control.CustomPopupControl-0.1',
        Observer : 'juliet.core.Observer-0.1'
    }),
    $ = juliet.jQuery;

    /**
     * Supported keyboard keys.
     */
    var KeyType = {

		UP : 38,

		DOWN : 40,

		RETURN : 13

	};

    var
    DEFAULT_FORMAT_TITLE = function(title) { return title; },
    MAX_LIST_SIZE = 10,
    LIST_ITEM_HEIGHT = 20,
    MAX_LIST_HEIGHT = MAX_LIST_SIZE * LIST_ITEM_HEIGHT,
    LIST_TEMPLATE = '<div class="popup-list"></div>',
    LIST_ITEM_TEMPLATE = '<div class="list-item"><span class="item-title">${title}</span>'
        + '<span class="item-value hidden">${value}</span></div>';

    var CustomPopupControlClass = $u('CustomPopupControl');

    var PopupListClass = function() {

        /**
         * Element that mouse cursor points to.
         *
         * @type JQueryElement
         * @private
         */
        this._highlightedElement = null;

        /**
         * List data (array of objects with "title" and "value" fields).
         *
         * @type Array
         * @private
         */
        this._data = [];

        /**
         * @see ObserverClass
         * @type Object
         * @private
         */
        this._observer = juliet.createObject($u('Observer'));

        /**
         * Popup list element.
         *
         * @type JQueryElement
         * @private
         */
        this._listElement = null;

        /**
         * Specify which values already adjusted.
         *
         * @type Object
         * @private
         */
        this._adjustedValues = { height : false, width : false, position : false };

        // -------------------------------------------------------
        // -                        LOGIC                        -
        // -------------------------------------------------------

        /**
         * @private
         */
        this._getCurrentDataRenderedHtml = function(titlePropertyName, valuePropertyName) {
            var
            formatTitle = this._options.formatTitle || DEFAULT_FORMAT_TITLE,
            html = '';

            $u('array').each(this._data, function(item) {
                html += $u('string').format(LIST_ITEM_TEMPLATE, {
                    title : formatTitle(item[titlePropertyName]),
                    value : item[valuePropertyName]
                });
            });

            return html;
        };

        /**
         * @private
         */
        this._attachIndexAttributeToListItems = function() {
            this._listElement.children().each(function(index) {
                $(this).attr('listIndex', index);
            });
        };

        /**
         * @private
         */
        this._initializePopupList = function() {
            this._listElement = $(LIST_TEMPLATE);
            this._listElement.addClass(this.uid);
            $(document.body).append(this._listElement);
        };

        //
        // ADJUST VALUES
        //

        /**
         * @private
         */
        this._adjustListScrollTop = function() {
            var
            scrollTop = this._listElement.scrollTop(),
            firstVisibleIndex = Math.ceil(scrollTop / LIST_ITEM_HEIGHT),
            lastVisibleIndex = Math.floor((scrollTop + MAX_LIST_HEIGHT) / LIST_ITEM_HEIGHT) - 1,
            currentIndex = this._highlightedElement.attr('listIndex');

            if (currentIndex < firstVisibleIndex) {
                this._listElement.scrollTop(currentIndex * LIST_ITEM_HEIGHT);
            } else if (currentIndex > lastVisibleIndex) {
                this._listElement.scrollTop((currentIndex - MAX_LIST_SIZE + 1) * LIST_ITEM_HEIGHT);
            }
        };

        /**
         * @private
         */
        this._adjustListHeight = function() {
            if (this._adjustedValues.height) return;

            this._adjustedValues.height = true;

            var listSize = ((this._data.length < MAX_LIST_SIZE) ? this._data.length : MAX_LIST_SIZE);

            this._listElement.css('height', LIST_ITEM_HEIGHT * listSize);
        };

        /**
         * @private
         */
        this._adjustListWidth = function() {
            if (this._adjustedValues.width) return;

            this._adjustedValues.width = true;
            this._listElement.css('width', this._options.ownerElement.outerWidth() - 2);
        };

        /**
         * @private
         */
        this._adjustListPosition = function() {
            if (this._adjustedValues.position) return;

            this._adjustedValues.position = true;

            var
            ownerElement = this._options.ownerElement,
            ownerElementHeight = ownerElement.outerHeight(),
            ownerElementOffset = ownerElement.offset();

            this._listElement.css({
                left : ownerElementOffset.left,
                top : ownerElementOffset.top + ownerElementHeight - 1
            });
        };

        /**
         * @private
         */
        this._adjustListSizeAndPosition = function() {
            this._adjustListWidth();
            this._adjustListHeight();
            this._adjustListPosition();
        };

        //
        // LIST ITEM HIGHLIGHTING
        //

        /**
         * @private
         */
        this._selectHighlightedItem = function() {
            this._observer.notify('item-selected', this._getHighlightedItem());
        };

        /**
         * @private
         */
        this._getHighlightedItem = function() {
            return {
                title : this._highlightedElement.find('.item-title').text(),
                value : this._highlightedElement.find('.item-value').html()
            };
        };

        /**
         * @private
         */
        this._highlightFirstListItem = function() {
            this._highlightListItem(this._listElement.children(':first'));
        };

        /**
         * @private
         */
        this._highlightPreviousListItem = function() {
            var previousItem = this._highlightedElement.prev();
            if (previousItem.length == 1) this._highlightListItem(previousItem);
        };

        /**
         * @private
         */
        this._highlightNextListItem = function() {
            var nextItem = this._highlightedElement.next();
            if (nextItem.length == 1) this._highlightListItem(nextItem);
        };

        /**
         * @private
         */
        this._highlightListItem = function(itemElement) {
            if (this._highlightedElement != null) {
                this._highlightedElement.removeClass('active');
            }

            itemElement.addClass('active');
            this._highlightedElement = itemElement;
        };

        //
        // UI EVENTS HANDLERS
        //

        /**
         * Try to find parent with class "list-item" of specified target.
         *
         * @param {DOMElement} target Dom element.
         * @private
         */
        this._fixListElementTarget = function(target) {
            return $u('dom').parent(target, 'list-item', 10);
        };

        /**
         * @param {Object} e Event object.
         * @private
         */
        this._listElementOnMouseOverHandler = function(e) {
            var targetDom = this._fixListElementTarget(e.target);
            if (!targetDom) return;
            this._highlightListItem($(targetDom));
        };

        /**
         * @param {Object} e Event object.
         * @private
         */
        this._listElementOnClickHandler = function(e) {
            var targetDom = this._fixListElementTarget(e.target);
            if (!targetDom) return;
            this._selectHighlightedItem();
        };

        /**
         * Handle supported keys and prevent default actions on them.
         *
         * @param {Object} e Event object.
         * @private
         */
        this._documentOnKeyDownHandler = function(e) {
            if (e.keyCode == KeyType.UP) {
                this._highlightPreviousListItem();
                this._adjustListScrollTop();
                return false;
            } else if (e.keyCode == KeyType.DOWN) {
                this._highlightNextListItem();
                this._adjustListScrollTop();
                return false;
            } else if (e.keyCode == KeyType.RETURN) {
                this._selectHighlightedItem();
                return false;
            }
        };

        /**
         * @private
         */
        this._bindGlobalEventsHandlers = function() {
            $(document).keydown($u('func').proxy(this._documentOnKeyDownHandler, this));
        };

        /**
         * @private
         */
        this._unbindGlobalEventsHandlers = function() {
            $(document).unbind('keydown', $u('func').proxy(this._documentOnKeyDownHandler));
        };

        /**
         * @private
         */
        this._bindEventsHandlers = function() {
            $u('func').bind(this._listElement, 'mouseover', this._listElementOnMouseOverHandler, this);
            $u('func').bind(this._listElement, 'click', this._listElementOnClickHandler, this);
        };

        //
        // API
        //

        /**
         * Fill list with new data.
         *
         * @param {Object} o Method params. Contains:
         *     - {Array} data Array of objects.
         *     - {String} titlePropertyName
         *     - {String} valuePropertyName
         */
        this.setData = function(o) {
            this._data = o.data || [];
            this._adjustedValues.height = false;

            // Data is empty -- nothing to do.
            if (this._data.length == 0) return;

            if (this.visible) {
                this._adjustListHeight();
            }

            this._listElement.html(this._getCurrentDataRenderedHtml(
                o.titlePropertyName || 'title',
                o.valuePropertyName || 'id'
            ));

            this._attachIndexAttributeToListItems();
            this._highlightFirstListItem();
        };

        this.show = function() {
            // Already visible or no data.
            if (this._visible || (this._data.length == 0)) return;

            this._visible = true;
            this._adjustListSizeAndPosition();
            this._listElement.show();
            this._bindGlobalEventsHandlers();

            this._popupHider.startMonitor();
        };

        this.hide = function(fromHider) {
            if (!this._visible) return;

            this._visible = false;
            this._listElement.hide();
            this._unbindGlobalEventsHandlers();

            this._popupHider.stopMonitor();

            if (fromHider) {
                this._observer.notify('hide-from-hider');
            }
        };

        /**
         * Initialize instance with specified options.
         *
         * @param {Object} o Instance options. Contains:
         *     - {JQueryElement} ownerElement
         *     - {Function} formatTitle
         */
        this.initialize = function(o) {
            // Call parent's initialize function.
            PopupListClass.prototype.initialize.call(this, o);

            this._initializePopupList();
            this._bindEventsHandlers();
        };

        // -------------------------------------------------------
        // -                      GET & SET                      -
        // -------------------------------------------------------

        this.getData = function() {
            return this._data;
        };

        this.getObserver = function() {
            return this._observer;
        };

        this.getListElement = function() {
            return this._listElement;
        };

    };

    // Extends "CustomPopupControlClass".
    PopupListClass.prototype = new CustomPopupControlClass();

    juliet.registerModule('juliet.ui.popup.control', 'PopupList-0.1', PopupListClass);

})();
